using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;
using System.Xml;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace SharpCast
{
    [ClassInterface(ClassInterfaceType.AutoDual), Serializable()]
    public class Directory
    {
        public enum DirectoryState { ready = 0, updating = 1, error = 2 };

        private List<Channel> _channels = new List<Channel>();

        [NonSerialized()]
        private DirectoryState _state = DirectoryState.ready;
        [NonSerialized()]
        private volatile int _updateProgress;
        [NonSerialized()]
        private volatile int _updateMax;

        private DateTime _created;
        private DateTime _modified;

        // Events
        public delegate void channelListener(Channel channel);
        [field: NonSerialized]
        public event channelListener channelAdded;
        [field: NonSerialized]
        public event channelListener channelRemoved;
        [field: NonSerialized]
        public event channelListener channelUpdated;

        public delegate void stateListener(DirectoryState state);
        [field: NonSerialized]
        public event stateListener stateUpdated;

        public Directory()
        {
            _created = DateTime.Now;
        }

        /// <summary>
        /// Loads a directory from a cached copy
        /// </summary>
        /// <returns>the loaded directory</returns>
        public static Directory loadCache(string file)
        {
            try
            {
                FileInfo f = new FileInfo(file);
                FileStream stream = f.OpenRead();

                BinaryFormatter formatter = new BinaryFormatter();
                Directory dir = (Directory) formatter.Deserialize(stream);
                stream.Close();

                // Refresh listeners
                foreach (Channel c in dir.channels)
                {
                    c.channelUpdated += dir.channelUpdateListener;
                }
                return dir;
            }
            catch (Exception e)
            {
                System.Windows.Forms.MessageBox.Show(e.Message + "/n" + e.StackTrace);
                return new Directory();
            }
        }

        /// <summary>
        /// Loads list of podcasts from an opml file
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public void load(string file)
        {
            // Load file
            XmlDocument document = new XmlDocument();
            document.Load(file);
            XmlNodeList list = document.DocumentElement.GetElementsByTagName("outline");

            ParameterizedThreadStart start = new ParameterizedThreadStart(loadUris);
            Thread thread = new Thread(start);
            _updateProgress = 0;
            _updateMax = list.Count;
            thread.Start(document);
        }

        private void loadUris(Object list)
        {
            foreach (XmlElement element in (XmlNodeList) list)
            {
                addChannel(new Channel(element.GetAttribute("url")));
                _updateProgress++;
            }
        }

        /// <summary>
        /// Save this directory to a cached copy
        /// </summary>
        /// <returns>true if the save was successful</returns>
        public bool saveCache(string file)
        {
            try
            {
                FileInfo f = new FileInfo(file);

                // Check folder exists
                if (!f.Directory.Exists)
                {
                    f.Directory.Create();
                }

                FileStream stream = f.OpenWrite();

                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(stream, this);
                stream.Flush();
                stream.Close();
                return true;
            }
            catch (Exception e)
            {
                System.Windows.Forms.MessageBox.Show(e.Message + "\n" + e.StackTrace.ToString());
                return false;
            }
        }

        /// <summary>
        /// Saves this list of podcasts as an opml file
        /// </summary>
        /// <param name="file"></param>
        /// <returns>true if the save was successful</returns>
        public bool save(string file)
        {
            try
            {
                // Open main output file
                FileInfo f = new FileInfo(file);
                if (f.Exists)
                {
                    f.MoveTo(f.FullName + ".bak");
                    f = new FileInfo(file);
                }
                StreamWriter output = new StreamWriter(f.OpenWrite());

                // Write header
                _modified = DateTime.Now;
                output.WriteLine("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>");
                output.WriteLine("<!-- OPML generated by SharpCast " + 
                    this.GetType().Assembly.GetName().Version +
                    " on " + _modified + "-->");
                output.WriteLine("<opml version=\"1.1\">");

                output.WriteLine(
                    "  <head>\n" +
                    "    <title>SharpCast Podcast Directory</title>\n" +
                    "    <dateCreated>" + _created + "</dateCreated>\n" +
                    "    <dateModified>" + _modified + "</dateModified>\n" +
                    "  </head>");

                // Write body
                output.WriteLine("  <body>");
                foreach (Channel channel in _channels)
                {
                    output.WriteLine("    <outline text=\"" + channel.title + 
                        "\" type=\"link\" url=\"" +
                        channel.link + "\" />");
                }
                output.WriteLine("  </body>");

                // Write footer
                output.WriteLine("</opml>");
                output.Close();
            }
            catch
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Updates the channels in this directory asynchronously
        /// </summary>
        public void update()
        {
            // Run update
            ThreadStart start = new ThreadStart(doUpdate);
            Thread thread = new Thread(start);
            _state = DirectoryState.updating;
            _updateProgress = 0;
            _updateMax = _channels.Count;
            thread.Start();
        }

        private void doUpdate()
        {
            try
            {
                foreach (Channel channel in _channels)
                {
                    _updateProgress++;
                    channel.update();
                    if (_updateProgress == _channels.Count)
                        _state = DirectoryState.ready;
                    if (stateUpdated != null)
                        stateUpdated(_state);
                }
            }
            catch
            {
                _state = DirectoryState.error;
                if (stateUpdated != null)
                    stateUpdated(_state);
            }
        }

        public int updateProgress
        {
            get { return _updateProgress; }
        }

        public int updateMax
        {
            get { return _updateMax; }
        }

        public List<Channel> channels
        {
            get { return _channels; }
        }

        public void addChannel(string url)
        {
            Channel c = new Channel(url);
            addChannel(c);
        }

        public void addChannel(Channel c)
        {
            _channels.Add(c);
            c.channelUpdated += channelUpdateListener;
            if (channelAdded != null)
                channelAdded(c);
        }

        public void removeChannel(Channel c)
        {
            if (_channels.Remove(c))
                c.channelUpdated -= channelUpdateListener;
            if (channelRemoved != null)
                channelRemoved(c);
        }

        public void channelUpdateListener(Channel c)
        {
            channelUpdated(c);
        }

        public DirectoryState state
        {
            get { return _state; }
        }

        public Episode getEpisodeByGuid(string guid)
        {
            foreach (Channel channel in _channels)
            {
                foreach (Episode episode in channel.episodes)
                {
                    if (episode.guid.Equals(guid) || 
                        guid.Equals(episode.url)) // legacy value from mmPodcasts v0.2
                        return episode;
                }
            }
            return null;
        }

        public void trackPlayed() {

        }
    }
}
